package tracing

import (
	"bytes"
	"context"
	"encoding/base64"
	"fmt"
	"os"

	"github.com/opentracing/opentracing-go"
	"google.golang.org/grpc/metadata"
)

// AGitTracingCarrierEnvKey key
const AGitTracingCarrierEnvKey = "AGIT_TRACING_CARRIER"

// AddAGitTracingCarrierEnv extracts tracing info from the passed in context
// and then encodes the info into binary format, the encoded binary data is
// then base64 encoded as the value of AGIT_TRACING_CARRIER
func AddAGitTracingCarrierEnv(ctx context.Context, env []string) []string {
	span := opentracing.SpanFromContext(ctx)
	if span == nil {
		return env
	}

	carrier := bytes.Buffer{}
	err := opentracing.GlobalTracer().Inject(opentracing.SpanFromContext(ctx).Context(), opentracing.Binary, &carrier)
	if err != nil {
		return env
	}
	env = append(env, fmt.Sprintf("%s=%s", AGitTracingCarrierEnvKey, base64.StdEncoding.EncodeToString(carrier.Bytes())))

	return env
}

// AGitStartSpanFromEnv starts an opentracing span from env AGIT_TRACING_CARRIER
func AGitStartSpanFromEnv(ctx context.Context, operationName string, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) {
	carrierContent := os.Getenv(AGitTracingCarrierEnvKey)
	if carrierContent == "" {
		return opentracing.StartSpanFromContext(ctx, operationName, opts...)
	}

	decodedContent, err := base64.StdEncoding.DecodeString(carrierContent)
	if err != nil {
		return opentracing.StartSpanFromContext(ctx, operationName, opts...)
	}

	spanFromEnv, err := opentracing.GlobalTracer().Extract(opentracing.Binary, bytes.NewReader(decodedContent))
	if err != nil {
		return opentracing.StartSpanFromContext(ctx, operationName, opts...)
	}

	opts = append(opts, opentracing.ChildOf(spanFromEnv))
	span := opentracing.StartSpan(operationName, opts...)
	finalCtx := opentracing.ContextWithSpan(ctx, span)

	carrier := metadataTextMap{}
	if err := opentracing.GlobalTracer().Inject(spanFromEnv, opentracing.HTTPHeaders, carrier); err == nil {
		// magically inject metadata into context, fake what server should do
		finalCtx = metadata.NewOutgoingContext(finalCtx, metadata.MD(carrier))
	}

	return span, finalCtx
}
